module cpu(
        input           i_wb_clk,
        input           i_wb_rst,
        input           i_wb_ack,
        input   [31:0]  i_wb_dat,
        output  [31:0]  o_wb_dat,
        output  [31:2]  o_wb_adr,
        output  [ 3:0]  o_wb_sel,
        output          o_wb_cyc,
        output          o_wb_stb,
        output          o_wb_we,
        output  [ 2:0]  o_wb_cti,
        output  [ 1:0]  o_wb_bte,
        input           i_irq,
        output          o_read_instr,
        output  [31:2]  o_pc,
        input           i_instr_ack,
        input   [31:0]  i_instr
);

`include "instructions.vh"

wire [32:0] wb_in_bundle;
wire [73:0] wb_out_bundle;

assign wb_in_bundle = {i_wb_ack,i_wb_dat};
assign {o_wb_cyc,o_wb_stb,o_wb_we,o_wb_adr,o_wb_sel,o_wb_dat,o_wb_cti,o_wb_bte} = wb_out_bundle;

localparam DE2_IR_WIDTH = 51;
localparam EX1_IR_WIDTH = 242;
localparam EX2_IR_WIDTH = 434;
localparam EX3_IR_WIDTH = 704;
localparam WB_IR_WIDTH = 435;

wire [127:0] gpr_read_out;
wire [16:0] gpr_read_in;
wire [31:0] cpsr;
wire [31:0] spsr;
wire [130:0] mul_in;
wire [64:0] mul_out;
wire [30:0] wb_branch_o;
wire [80:0] gpr_write_in;
wire [16:0] cpsr_write_in;
wire [38:0] spsr_write_in;
wire [3:0] cpsr_nzcv;
wire [2:0] cpsr_aif;
wire [4:0] cpsr_mode;
wire [50:0] coproc_in;

wire fe1_valid_next;
wire pc_we;
wire fe1_valid;
wire de1_ir_we;
wire de1_valid;
wire de2_ir_we;
wire ex1_done_o;
wire ex1_ir_we;
wire ex2_ir_we;
wire ex3_ir_we;
wire ex3_update_o;
wire ex3_done_o;
wire de2_valid;
wire ex1_valid;
wire ex2_valid;
wire ex3_valid;
wire wb_valid;

wire de2_ir_dsel;

wire [DE2_IR_WIDTH-1:0] de1_o;
wire [DE2_IR_WIDTH-1:0] de2;
wire [EX1_IR_WIDTH-1:0] de2_o;
wire [EX1_IR_WIDTH-1:0] ex1;
wire [EX1_IR_WIDTH-1:0] ex1_fb_o;
wire [EX2_IR_WIDTH-1:0] ex1_o;
wire [EX2_IR_WIDTH-1:0] ex2;
wire [EX3_IR_WIDTH-1:0] ex2_o;
wire [EX3_IR_WIDTH-1:0] ex3;
wire [EX3_IR_WIDTH-1:0] ex3_fb_o;
wire [WB_IR_WIDTH-1:0] ex3_o;
wire [WB_IR_WIDTH-1:0] wb;

assign o_read_instr = fe1_valid;

assign cpsr = {cpsr_nzcv, 19'b0, cpsr_aif, 1'b0, cpsr_mode};

regfile_wrapper u_regfile_wrapper(
  .clk(i_wb_clk),
  .mode(cpsr_mode),
  .read_in_bundle(gpr_read_in),
  .read_out_bundle(gpr_read_out),
  .write_in_bundle(gpr_write_in)
);

cpsr_wrapper u_cpsr_wrapper(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .write_in_bundle(cpsr_write_in),
  .nzcv(cpsr_nzcv),
  .aif(cpsr_aif),
  .mode(cpsr_mode)
);

spsr_wrapper u_spsr_wrapper(
  .clk(i_wb_clk),
  .mode(cpsr_mode),
  .write_in_bundle(spsr_write_in),
  .q(spsr)
);

mul_wrapper u_mul_wrapper(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .in_bundle(mul_in),
  .out_bundle(mul_out)
);

pipeline_control u_pipeline_control(
  .i_ex1_done(ex1_done_o),
  .i_ex3_update(ex3_update_o),
  .i_ex3_done(ex3_done_o),
  .i_fe1_valid(fe1_valid),
  .i_de1_valid(de1_valid),
  .i_de2_valid(de2_valid),
  .i_ex1_valid(ex1_valid),
  .i_ex2_valid(ex2_valid),
  .i_ex3_valid(ex3_valid),
  .i_wb_valid(wb_valid),
  .i_instr_ack(i_instr_ack),
  .i_irq(i_irq),
  .i_irq_mask(cpsr_aif[1]),
  .o_fe1_valid_next(fe1_valid_next),
  .o_pc_we(pc_we),
  .o_de1_ir_we(de1_ir_we),
  .o_de2_ir_we(de2_ir_we),
  .o_ex1_ir_we(ex1_ir_we),
  .o_ex2_ir_we(ex2_ir_we),
  .o_ex3_ir_we(ex3_ir_we),
  .o_de2_ir_dsel(de2_ir_dsel)
);

pc_wrapper u_pc_wrapper(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(pc_we),
  .in_bundle(wb_branch_o),
  .q(o_pc)
);

coproc_wrapper u_coproc_wrapper(
  .i_clk(i_wb_clk),
  .i_rst(i_wb_rst),
  .i_cmd(coproc_in)
);

pipeline_reg #(1,1) u_ir_fe1(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(1'b1),
  .d(fe1_valid_next),
  .q(fe1_valid)
);

pipeline_reg #(1) u_ir_de1(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(de1_ir_we),
  .d(fe1_valid),
  .q(de1_valid)
);

decode1_wrapper u_decode1_wrapper(
  .i_pipe(de1_valid),
  .o_pipe(de1_o),
  .i_instr(i_instr)
);

pipeline_reg2 #(DE2_IR_WIDTH) u_ir_de2(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(de2_ir_we),
  .dsel(de2_ir_dsel),
  .d0(de1_o),
  .d1({1'b1,4'b1110,28'bx,1'b1,`NUM_INSTR'b0}),
  .q(de2)
);

decode2_wrapper u_decode2_wrapper(
  .i_pipe(de2),
  .o_pipe(de2_o),
  .i_pc(o_pc+30'd2), // FIXME
  .o_valid(de2_valid)
);

pipeline_reg2 #(EX1_IR_WIDTH) u_ir_ex1(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(ex1_ir_we),
  .dsel(ex1_done_o),
  .d0(ex1_fb_o),
  .d1(de2_o),
  .q(ex1)
);

execute1_wrapper u_execute1_wrapper(
  .i_pipe(ex1),
  .o_pipe(ex1_o),
  .o_fb(ex1_fb_o),
  .o_done(ex1_done_o),
  .i_gpr(gpr_read_out),
  .o_gpr(gpr_read_in),
  .i_cpsr(cpsr),
  .i_spsr(spsr),
  .o_valid(ex1_valid)
);

pipeline_reg #(EX2_IR_WIDTH) u_ir_ex2(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(ex2_ir_we),
  .d(ex1_o),
  .q(ex2)
);

execute2_wrapper u_execute2_wrapper(
  .i_pipe(ex2),
  .o_pipe(ex2_o),
  .o_valid(ex2_valid)
);

pipeline_reg2 #(EX3_IR_WIDTH) u_ir_ex3(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(ex3_ir_we),
  .dsel(ex3_done_o),
  .d0(ex3_fb_o),
  .d1(ex2_o),
  .q(ex3)
);

execute3_wrapper u_execute3_wrapper(
  .i_pipe(ex3),
  .o_pipe(ex3_o),
  .o_fb(ex3_fb_o),
  .o_update(ex3_update_o),
  .o_done(ex3_done_o),
  .i_wb(wb_in_bundle),
  .i_mul(mul_out),
  .o_wb(wb_out_bundle),
  .o_mul(mul_in),
  .o_valid(ex3_valid),
  .o_coproc(coproc_in)
);

pipeline_reg #(WB_IR_WIDTH) u_ir_wb(
  .clk(i_wb_clk),
  .rst(i_wb_rst),
  .we(1'b1),
  .d(ex3_o),
  .q(wb)
);

writeback_wrapper u_writeback_wrapper(
  .i_pipe(wb),
  .o_branch(wb_branch_o),
  .o_gpr(gpr_write_in),
  .o_cpsr(cpsr_write_in),
  .o_spsr(spsr_write_in),
  .o_valid(wb_valid)
);

endmodule
